package com.shiehub.sdk.opengw.util;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * SHIE
 *
 * @Author: guoleijie@shie.com.cn
 * @Description: SHIE的http客户端
 * @date 2016年10月8日
 */
public class ShieHttpUtil {

  private static final String DEFAULT_CHARSET = "UTF-8";
  private static final String USER_AGENT = "sdk-java";
  private static final String JOSN_ACCEPT = "application/json;charset=UTF-8";
  private static final String XML_ACCEPT = "application/xml;charset=UTF-8";
  static String ACCEPT = null;

  public static String doPost(String url, String strContent, int connectTimeout, int readTimeout,
      String flag, Map<String, Object> proxyMap)
      throws IOException {
    if (flag.equals("xml")) {
      ACCEPT = XML_ACCEPT;
    } else {
      ACCEPT = JOSN_ACCEPT;
    }
    return doPost(proxyMap, url, strContent, "UTF-8", connectTimeout, readTimeout);
  }

  public static String doPost(Map<String, Object> proxyMap, String url, String strContent,
      String charset, int connectTimeout, int readTimeout)
      throws IOException {
    return doPost(proxyMap, url, strContent, charset, connectTimeout, readTimeout, null);
  }

  public static String doPost(Map<String, Object> proxyMap, String url, String strContent,
      String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap)
      throws IOException {
    byte[] content = new byte[0];
    if (strContent != null) {
      content = strContent.getBytes(charset);
    }
    return _doPost(proxyMap, url, ACCEPT, content, connectTimeout, readTimeout, headerMap);
  }

  private static String _doPost(Map<String, Object> proxyMap, String url, String ctype,
      byte[] content, int connectTimeout, int readTimeout, Map<String, String> headerMap)
      throws IOException {
    HttpURLConnection conn = null;
    OutputStream out = null;
    String rsp = null;
    try {
      conn = getConnection(proxyMap, new URL(url), "POST", ctype, headerMap);
      conn.connect();
      conn.setConnectTimeout(connectTimeout);
      conn.setReadTimeout(readTimeout);
      out = conn.getOutputStream();
      out.write(content);
      rsp = getResponseAsString(conn);
    } finally {
      if (out != null) {
        out.close();
      }
      if (conn != null) {
        conn.disconnect();
      }
    }

    return rsp;
  }

  public static String buildQuery(Map<String, String> params, String charset) throws IOException {
    if ((params == null) || (params.isEmpty())) {
      return null;
    }
    StringBuilder query = new StringBuilder();
    Set<Entry<String, String>> entries = params.entrySet();
    boolean hasParam = false;
    for (Entry<String, String> entry : entries) {
      String name = (String) entry.getKey();
      String value = (String) entry.getValue();

      if (StringUtils.areNotEmpty(new String[]{name, value})) {
        if (hasParam) {
          query.append("&");
        } else {
          hasParam = true;
        }
        query.append(name).append("=").append(URLEncoder.encode(value, charset));
      }
    }
    return query.toString();
  }

  private static HttpURLConnection getConnection(Map<String, Object> proxyMap, URL url,
      String method, String ctype, Map<String, String> headerMap) throws IOException {
    Proxy proxy = null;
    String user = null;
    String password = null;
    if (proxyMap != null && !proxyMap.isEmpty()) {
      String host = (String) proxyMap.get("host");
      Integer port = (Integer) proxyMap.get("port");
      user = (String) proxyMap.get("user");
      password = (String) proxyMap.get("password");
      if (host != null && port == null) {
        port = 80;
      }
      proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));

    }

    HttpURLConnection conn = null;
    HttpsURLConnection connHttps;
    if ("https".equals(url.getProtocol())) {
      SSLContext ctx = null;
      try {
        ctx = SSLContext.getInstance("TLS");
        ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()},
            new SecureRandom());
      } catch (Exception e) {
        throw new IOException();
      }
      if (proxy != null) {
        connHttps = (HttpsURLConnection) url.openConnection(proxy);
      } else {

        connHttps = (HttpsURLConnection) url.openConnection();
      }
      connHttps.setSSLSocketFactory(ctx.getSocketFactory());
      connHttps.setHostnameVerifier(new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
          return true;
        }
      });
      conn = connHttps;
    } else {
      if (proxy != null) {
        conn = (HttpURLConnection) url.openConnection(proxy);
      } else {
        conn = (HttpURLConnection) url.openConnection();
      }
    }

    if (user != null && password != null) {
      String headerKey = "Proxy-Authorization";
      String headerValue = "Basic " + Base64.encode((user + ":" + password).getBytes());
      conn.setRequestProperty(headerKey, headerValue.trim());
    }

    conn.setRequestMethod(method);
    conn.setDoInput(true);
    conn.setDoOutput(true);
    conn.setRequestProperty("Accept", ACCEPT);
    conn.setRequestProperty("User-Agent", USER_AGENT);
    conn.setRequestProperty("Content-Type", ctype);
    if (headerMap != null) {
      for (Entry<String, String> entry : headerMap.entrySet()) {
        conn.setRequestProperty((String) entry.getKey(), (String) entry.getValue());
      }
    }
    return conn;
  }

  protected static String getResponseAsString(HttpURLConnection conn) throws IOException {
    String charset = getResponseCharset(conn.getContentType());
    InputStream es = conn.getErrorStream();
    if (es == null) {
      return getStreamAsString(conn.getInputStream(), charset);
    }
    String msg = getStreamAsString(es, charset);
    if (StringUtils.isEmpty(msg)) {
      throw new IOException(conn.getResponseCode() + ":" + conn.getResponseMessage());
    }
    return msg;
  }

  private static String getResponseCharset(String ctype) {
    String charset = DEFAULT_CHARSET;
    if (StringUtils.isNotEmpty(ctype)) {
      String[] params = ctype.split(";");
      for (String param : params) {
        param = param.trim();
        if (param.startsWith("charset")) {
          String[] pair = param.split("=", 2);
          if ((pair.length != 2) ||
              (StringUtils.isEmpty(pair[1]))) {
            break;
          }
          charset = pair[1].trim();
          break;
        }
      }
    }
    return charset;
  }

  private static String getStreamAsString(InputStream stream, String charset) throws IOException {
    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset), 8192);
      StringWriter writer = new StringWriter();

      char[] chars = new char[8192];
      int count = 0;
      while ((count = reader.read(chars)) > 0) {
        writer.write(chars, 0, count);
      }

      return writer.toString();
    } finally {
      if (stream != null) {
        stream.close();
      }
    }
  }

  private static class DefaultTrustManager
      implements X509TrustManager {

    public X509Certificate[] getAcceptedIssuers() {
      return null;
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
    }
  }
}
